The following map shows all buildable area for attached and detached ADUs in East Palo Alto.

load("epa_parcels_furthest_adu.Rdata")

map <- epa_parcels_furthest_adu

leaflet() %>%
  addTiles(group = "Default") %>% 
  addProviderTiles(providers$Esri.WorldImagery, group = "Satellite") %>% 
  addPolygons(
    data = map %>% st_set_geometry("parcel_geometry") %>% dplyr::select(ADDRESS) %>% st_transform(4326),
    fill = F, 
    color = "white",
    weight = 0.5,
    opacity = 0.5,
    highlightOptions = highlightOptions(
      weight = 2,
      opacity = 1
    ),
    label = ~ADDRESS,
    group = "Parcels"
  ) %>% 
  addPolygons(
    data = map %>% st_set_geometry("attached_buildable_area_geometry") %>% dplyr::select(max_attached_buildable_area) %>% st_transform(4326) %>% filter(!is.na(st_dimension(.))),
    fillColor = "yellow", 
    stroke = F,
    fillOpacity = 0.5,
    highlightOptions = highlightOptions(
      fillOpacity = 1
    ),
    label = ~max_attached_buildable_area,
    group = "Attached ADU Buildable Area"
  ) %>% 
  addPolygons(
    data = map %>% st_set_geometry("detached_buildable_area_geometry") %>% dplyr::select(max_detached_buildable_area) %>% st_transform(4326) %>% filter(!is.na(st_dimension(.))),
    fillColor = "green", 
    stroke = F,
    fillOpacity = 0.5,
    highlightOptions = highlightOptions(
      fillOpacity = 1
    ),
    label = ~max_detached_buildable_area,
    group = "Detached ADU Buildable Area"
  ) %>% 
  addPolygons(
    data = map %>% st_set_geometry("building_geometry") %>% dplyr::select(`GROSS BUILDING SQFT`) %>% st_transform(4326) %>% filter(!is.na(st_dimension(.))),
    fillColor = "tan", 
    stroke = F,
    fillOpacity = 0.5,
    highlightOptions = highlightOptions(
      fillOpacity = 1
    ),
    label = ~`GROSS BUILDING SQFT`,
    group = "Existing Building"
  ) %>% 
  addPolygons(
    data = map %>% st_set_geometry("furthest_adu") %>% st_transform(4326) %>% filter(!is.na(st_dimension(.))),
    fillColor = "blue", 
    stroke = F,
    fillOpacity = 0.5,
    highlightOptions = highlightOptions(
      fillOpacity = 1
    ),
    group = "Example Detached ADU"
  ) %>% 
  addLayersControl(
    baseGroups = c("Default", "Satellite"),
    overlayGroups = c("Parcels", "Existing Building", "Attached ADU<br>Buildable Area", "Detached ADU<br>Buildable Area", "Example<br>Detached ADU"),
    options = layersControlOptions(collapsed = T)
  )
# include_url("https://city.systems/epa-adu/epa_parcels_adus_map.html")

\(~\)

Next are some summary statistics.

First, here’s the breakdown of parcels by zoning type in General Plan 2035. The parcel shapes and zoning were pulled from ArcGISOnline. Note there were some inconsistencies in APN designation between this zoning file and the County’s Assessor record. 6 residential parcels were manually re-coded from Assessor APN to EPA General Plan APN.

zoning_summary <-
  epa_parcels_furthest_adu %>%
  st_set_geometry(NULL) %>% 
  group_by(Zoning) %>% 
  summarize(
    Count = n(),
    `Median Lot Size` = median(parcel_area, na.rm=T) %>% round()
  ) %>% 
  filter(!is.na(Zoning))
  
kable(
  zoning_summary, 
  booktabs = TRUE,
  caption = 'Zoning summary'
  ) %>%
  kable_styling() %>%
  scroll_box(width = "100%")
Zoning summary
Zoning Count Median Lot Size
4 Corners 41 9813
Bay Road Central 28 10184
C-G (Commercial General) 9 17606
C-N (Commercial Neighborhood) 15 8573
C-O (Commercial Office) 12 16029
Industrial Flex Overlay 12 13883
Industrial Transition 17 13654
MUC-1 (Mixed Use Corridor) 172 9994
MUC-2 (Mixed Use Corridor) 8 12986
MUH (Mixed Use High) 12 38825
MUL (Mixed Use Low) 31 8114
PI (Public Institutional) 19 81278
PR (Parks and Recreation) 34 23476
PUD (Planned Unit Development) 219 3102
R-HD (Multi-Family High Density Residential) 284 30208
R-LD (Single-Family Residential) 3518 5603
R-MD-1 (Multi-Family Medium Density Residential) 300 6244
R-MD-2 (Multi-Family Medium Density Residential) 155 6667
R-UHD (Multi-Family Urban High Density Residential) 46 30978
REC (Ravenswood Employment Center) 30 40473
RM (Resource Management) 5 228188
Urban Residential 81 10151
Waterfront Office 11 85561

\(~\)

New state regs require ADUs to be allowed in all residentially zoned parcels. We select: R-HD, R-LD, R-MD-1, R-MD-2, R-UHD, & Urban Residential.

The following table is the same as above but just for residential zones, and adding existing structure size and special flood hazard area status.

residential_summary <-
  epa_parcels_furthest_adu %>%
  st_set_geometry(NULL) %>% 
  filter(
    Zoning %in% 
      c(
        "R-HD (Multi-Family High Density Residential)",
        "R-LD (Single-Family Residential)",
        "R-MD-1 (Multi-Family Medium Density Residential)",
        "R-MD-2 (Multi-Family Medium Density Residential)",
        "R-UHD (Multi-Family Urban High Density Residential)",
        "Urban Residential"
      )
  ) %>% 
  group_by(Zoning) %>% 
  summarize(
    Count = n(),
    `Median Lot Size` = median(parcel_area, na.rm=T) %>% round(),
    `Median Gross Bldg Sqft` = median(`GROSS BUILDING SQFT`, na.rm=T) %>% round(),
    `In Flood Zone` = sum(!is.na(FLD_ZONE))
  ) %>% 
  filter(!is.na(Zoning))

# hist(epa_parcels_adus %>% filter(Zoning == "R-LD (Single-Family Residential)") %>% pull(parcel_area), breaks=c(seq(0,1000000,500)),xlim = c(0,10000))

kable(
  residential_summary, 
  booktabs = TRUE,
  caption = 'Residential zoning summary'
  ) %>%
  kable_styling() %>%
  scroll_box(width = "100%")
Residential zoning summary
Zoning Count Median Lot Size Median Gross Bldg Sqft In Flood Zone
R-HD (Multi-Family High Density Residential) 284 30208 10658 35
R-LD (Single-Family Residential) 3518 5603 1110 1171
R-MD-1 (Multi-Family Medium Density Residential) 300 6244 2072 101
R-MD-2 (Multi-Family Medium Density Residential) 155 6667 1340 0
R-UHD (Multi-Family Urban High Density Residential) 46 30978 26408 0
Urban Residential 81 10151 1690 7

\(~\)

Of residentially zoned parcels, below is the breakdown of property use code according to the County Assessor, which should represent as of early 2019 the existing use. The table also notes which PUCs we considered to be “ADU eligible” for our analysis, and completed the buildable area analysis for. Note the following key caveats:

The table includes some additional statistics. % owner occupied is the number for which there is a listed homeowner tax exemption OR the owner’s mailing address matches the property address.

residential_puc_summary <-
  epa_parcels_furthest_adu %>%
  st_set_geometry(NULL) %>% 
  filter(
    Zoning %in% 
      c(
        "R-HD (Multi-Family High Density Residential)",
        "R-LD (Single-Family Residential)",
        "R-MD-1 (Multi-Family Medium Density Residential)",
        "R-MD-2 (Multi-Family Medium Density Residential)",
        "R-UHD (Multi-Family Urban High Density Residential)",
        "Urban Residential"
      )
  ) %>% 
  group_by(PUC) %>% 
  summarize(
    Count = n(),
    `Median Lot Size` = median(parcel_area, na.rm=T) %>% round(),
    `Median Gross Bldg Sqft` = median(`GROSS BUILDING SQFT`, na.rm=T) %>% round(),
    `In Flood Zone` = sum(!is.na(FLD_ZONE)),
    `# of Units` = sum(UNITS,na.rm=T),
    `% Owner Occupied` = sum(`EXEMPTION CODE`=="HO" | ADDRESS != substr(`OWNER MAILING ADDRESS`,1,nchar(ADDRESS)),na.rm=T)/n()*100,
    `% Owner Occupied` = `% Owner Occupied` %>% round()
  ) %>% 
  filter(!is.na(PUC)) %>% 
  filter(PUC <= 5 | PUC >= 89) %>% 
  left_join(read_csv("smc_puc.csv"), by="PUC") %>% 
  dplyr::select(PUC, `PUC Description`, Categorization = type, everything())

kable(
  residential_puc_summary, 
  booktabs = TRUE,
  caption = 'Residential PUC summary'
  ) %>%
  kable_styling() %>%
  scroll_box(width = "100%")
Residential PUC summary
PUC PUC Description Categorization Count Median Lot Size Median Gross Bldg Sqft In Flood Zone # of Units % Owner Occupied
0 VACANT LAND other 69 6112 NA 13 0 99
1 SINGLE FAMILY RES adu eligible 3603 5561 1110 1157 0 78
2 DUPLEX adu eligible 49 5996 1990 12 86 94
3 TRIPLEX adu eligible 3 4997 1895 0 9 100
4 FOURPLEX adu eligible 9 7129 2836 5 40 100
5 FIVE OR MORE UNITS other residential 85 19993 18018 22 1854 99
89 RESIDENTIAL MISC. adu eligible 2 7583 0 0 0 50
91 MORE THAN 1 DETACHED LIVING UNITS adu eligible 27 7524 2181 5 27 85
92 SFR CONVERTED TO 2 UNITS adu eligible 38 6582 2210 12 28 89
93 SFR & DUPLEX OR TRIPLEX adu eligible 4 9001 2950 3 7 100
94 TWO DUPLEXES adu eligible 6 7033 2388 3 24 100
95 RESIDENTIAL; COMBINATION OF UNIT TYPES adu eligible 7 8211 2699 4 25 86
96 FOURPLEX PLUS A RESIDENCE, DUPLEX OR TRI adu eligible 6 9593 4810 4 39 100
97 RESIDENTIAL CONDO other residential 279 154490 NA 51 0 85

\(~\)

The following shows ADU statistics for the residential parcels.

epa_parcels_adus_residential <-
  epa_parcels_furthest_adu %>%
  st_set_geometry(NULL) %>% 
  filter(
    Zoning %in% 
      c(
        "R-HD (Multi-Family High Density Residential)",
        "R-LD (Single-Family Residential)",
        "R-MD-1 (Multi-Family Medium Density Residential)",
        "R-MD-2 (Multi-Family Medium Density Residential)",
        "R-UHD (Multi-Family Urban High Density Residential)",
        "Urban Residential"
      )
  )

epa_parcels_adus_residential_no9192 <-
  epa_parcels_adus_residential %>% 
  filter(!PUC %in% c(91,92)) %>% 
  filter(PUC <= 5 | PUC >= 89)

residential_adu_summary <-
  rbind(
    data.frame(
      Description = "Total # of Parcels",
      Amount = epa_parcels_adus_residential %>% nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    ),
    data.frame(
      Description = "Total # of ADU-Eligible Parcels",
      Amount = epa_parcels_adus_residential %>% filter(PUC <= 5 | PUC >= 89) %>%  nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential %>% filter(!PUC %in% c(91,92)) %>% filter(PUC <= 5 | PUC >= 89) %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    ),
    data.frame(
      Description = "Estimated # of Permitted ADUs",
      Amount = epa_parcels_adus_residential %>% filter(PUC %in% c(91,92)) %>%  nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential %>% filter(PUC %in% c(91,92)) %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    ),
    data.frame(
      Description = "Estimated # of Ministerial JADUs",
      Amount = epa_parcels_adus_residential_no9192 %>% nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential_no9192 %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    ),
    data.frame(
      Description = "Estimated # of Ministerial Attached/Detached ADUs (up to 800sqft)",
      Amount = epa_parcels_adus_residential_no9192 %>% filter(max_attached_buildable_area <= 800 | max_detached_buildable_area <= 800) %>% nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential_no9192 %>% filter(max_attached_buildable_area <= 800 | max_detached_buildable_area <= 800) %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    ),
    data.frame(
      Description = "Estimated # of Ministerial Attached ADUs (up to 800sqft)",
      Amount = epa_parcels_adus_residential_no9192 %>% filter(max_attached_buildable_area <= 800) %>% nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential_no9192 %>% filter(max_attached_buildable_area <= 800) %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    ),
    data.frame(
      Description = "Estimated # of Ministerial Detached ADUs (up to 800sqft)",
      Amount = epa_parcels_adus_residential_no9192 %>% filter(max_detached_buildable_area <= 800) %>% nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential_no9192 %>% filter(max_detached_buildable_area <= 800) %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    ),
    data.frame(
      Description = "Estimated # of Parcels under Local Influence (800-1200sqft)",
      Amount = epa_parcels_adus_residential_no9192 %>% filter(max_attached_buildable_area > 800 | max_detached_buildable_area > 800) %>% nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential_no9192 %>% filter(max_attached_buildable_area > 800 | max_detached_buildable_area > 800) %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    ),
    data.frame(
      Description = "Estimated # of Attached ADUs under Local Influence (800-1200sqft)",
      Amount = epa_parcels_adus_residential_no9192 %>% filter(max_attached_buildable_area > 800) %>% nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential_no9192 %>% filter(max_attached_buildable_area > 800) %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    ),
    data.frame(
      Description = "Estimated # of Detached ADUs under Local Influence (800-1200sqft)",
      Amount = epa_parcels_adus_residential_no9192 %>% filter(max_detached_buildable_area > 800) %>% nrow(),
      `Amount in Flood Zone` = epa_parcels_adus_residential_no9192 %>% filter(max_detached_buildable_area > 800) %>% filter(!is.na(FLD_ZONE)) %>% nrow()
    )
  ) %>% 
  rename(`Amount in Flood Zone` = Amount.in.Flood.Zone)

kable(
  residential_adu_summary, 
  booktabs = TRUE,
  caption = 'Residential ADU summary'
  ) %>%
  kable_styling() %>%
  scroll_box(width = "100%")
Residential ADU summary
Description Amount Amount in Flood Zone
Total # of Parcels 4384 1314
Total # of ADU-Eligible Parcels 4187 1274
Estimated # of Permitted ADUs 65 17
Estimated # of Ministerial JADUs 4122 1274
Estimated # of Ministerial Attached/Detached ADUs (up to 800sqft) 3125 1055
Estimated # of Ministerial Attached ADUs (up to 800sqft) 2977 1016
Estimated # of Ministerial Detached ADUs (up to 800sqft) 1228 392
Estimated # of Parcels under Local Influence (800-1200sqft) 1849 664
Estimated # of Attached ADUs under Local Influence (800-1200sqft) 421 125
Estimated # of Detached ADUs under Local Influence (800-1200sqft) 1692 621

\(~\)

Here’s a map showing some of the different circumstances from the table above. You can turn the flood layer on and off.

adu_summary_map <-
  epa_parcels_furthest_adu %>%
  mutate(
    Category =
      case_when(
        PUC %in% c(91,92) ~ "Existing Permitted ADU",
        max_detached_buildable_area > 800 ~ "Detached ADUs under Local Influence (800-1200sqft)",
        max_attached_buildable_area > 800 ~ "Attached ADUs under Local Influence (800-1200 sqft)",
        max_detached_buildable_area <= 800 ~ "Ministerial Detached ADUs (up to 800 sqft)",
        max_attached_buildable_area <= 800 ~ "Ministerial Attached ADUs (up to 800 sqft)",
        Zoning %in% 
      c(
        "R-HD (Multi-Family High Density Residential)",
        "R-LD (Single-Family Residential)",
        "R-MD-1 (Multi-Family Medium Density Residential)",
        "R-MD-2 (Multi-Family Medium Density Residential)",
        "R-UHD (Multi-Family Urban High Density Residential)",
        "Urban Residential"
      ) & (PUC <= 5 | PUC >= 89) ~ "Ministerial JADUs",
        TRUE ~ "Other"
      )
  ) %>% 
  mutate(
    Category = ifelse(Category == "Other", NA, Category)
  ) %>% 
  dplyr::select(ADDRESS,Category)

load("epa_sfha.Rdata")

epa_boundary <-
  places("CA", cb=FALSE) %>% 
  filter(NAME == "East Palo Alto") %>% 
  st_transform(projection)

epa_sfha_clip <-
  st_intersection(epa_sfha, epa_boundary)

leaflet() %>% 
  addTiles(group = "Default") %>% 
  addProviderTiles(providers$Esri.WorldImagery, group = "Satellite") %>% 
  addPolygons(
    data = adu_summary_map %>% filter(Category == "Existing Permitted ADU") %>% st_transform(4326),
    fillColor = "purple",
    color = "white",
    weight = 0.5,
    opacity = 0.5,
    fillOpacity = 0.75,
    highlightOptions = highlightOptions(
      weight = 2,
      opacity = 1
    ),
    label = ~ADDRESS,
    group = "Existing Permitted ADU"
  ) %>% 
  addPolygons(
    data = adu_summary_map %>% filter(Category == "Detached ADUs under Local Influence (800-1200sqft)") %>% st_transform(4326),
    fillColor = "yellow",
    color = "white",
    weight = 0.5,
    opacity = 0.5,
    fillOpacity = 0.5,
    highlightOptions = highlightOptions(
      weight = 2,
      opacity = 1
    ),
    label = ~ADDRESS,
    group = "Detached ADUs under Local Influence (800-1200sqft)"
  ) %>% 
  addPolygons(
    data = adu_summary_map %>% filter(Category == "Attached ADUs under Local Influence (800-1200 sqft)") %>% st_transform(4326),
    fillColor = "orange",
    color = "white",
    weight = 0.5,
    opacity = 0.5,
    fillOpacity = 0.75,
    highlightOptions = highlightOptions(
      weight = 2,
      opacity = 1
    ),
    label = ~ADDRESS,
    group = "Attached ADUs under Local Influence (800-1200sqft)"
  ) %>% 
  addPolygons(
    data = adu_summary_map %>% filter(Category == "Ministerial Detached ADUs (up to 800 sqft)") %>% st_transform(4326),
    fillColor = "green",
    color = "white",
    weight = 0.5,
    opacity = 0.5,
    fillOpacity = 0.75,
    highlightOptions = highlightOptions(
      weight = 2,
      opacity = 1
    ),
    label = ~ADDRESS,
    group = "Ministerial Detached ADUs (up to 800 sqft)"
  ) %>% 
  addPolygons(
    data = adu_summary_map %>% filter(Category == "Ministerial Attached ADUs (up to 800 sqft)") %>% st_transform(4326),
    fillColor = "green",
    color = "white",
    weight = 0.5,
    opacity = 0.5,
    fillOpacity = 0.75,
    highlightOptions = highlightOptions(
      weight = 2,
      opacity = 1
    ),
    label = ~ADDRESS,
    group = "Ministerial Attached ADUs (up to 800 sqft)"
  ) %>% 
  addPolygons(
    data = adu_summary_map %>% filter(Category == "Ministerial JADUs") %>% st_transform(4326),
    fillColor = "green",
    color = "white",
    weight = 0.5,
    opacity = 0.5,
    fillOpacity = 0.75,
    highlightOptions = highlightOptions(
      weight = 2,
      opacity = 1
    ),
    label = ~ADDRESS,
    group = "Ministerial JADUs"
  ) %>% 
  addPolygons(
    data = epa_sfha_clip %>% st_transform(4326),
    fillColor = "blue",
    stroke = F,
    fillOpacity = 0.25,
    group = "Special Flood Hazard Area"
  ) %>% 
  addLayersControl(
    baseGroups = c("Default", "Satellite"),
    overlayGroups = c("Existing Permitted ADU","Detached ADUs under<br>Local Influence (800-1200sqft)", "Attached ADUs under<br>Local Influence (800-1200sqft)","Ministerial Detached<br>ADUs (up to 800 sqft)","Ministerial Attached<br>ADUs (up to 800 sqft)", "Ministerial JADUs","Special Flood Hazard Area"),
    options = layersControlOptions(collapsed = T)
  )

\(~\)

Here’s the distribution of buildable area for attached ADUs.

hist(epa_parcels_furthest_adu$max_attached_buildable_area, breaks=c(seq(0,1200,50)),xlim = c(0,1200))

\(~\)

Here’s the distribution of buildable area for detached ADUs.

hist(epa_parcels_furthest_adu$max_detached_buildable_area, breaks=c(seq(0,1200,50)),xlim = c(0,1200))

\(~\)

More summary statistics coming soon.